package com.sghd.fight.module.game.service;

import com.sghd.common.console.ConsoleBean;
import com.sghd.common.console.ConsoleCommand;
import com.sghd.common.event.Event;
import com.sghd.common.event.EventBus;
import com.sghd.common.socket.core.ResultCallback;
import com.sghd.common.utils.ManagedException;
import com.sghd.common.utils.json.JsonUtils;
import com.sghd.fight.module.game.config.SkillConfig;
import com.sghd.fight.module.game.core.Grid;
import com.sghd.fight.module.game.core.model.Unit;
import com.sghd.fight.module.game.core.model.UnitState;
import com.sghd.fight.module.game.event.GameServerEvent;
import com.sghd.fight.module.game.event.PushEvent;
import com.sghd.fight.module.game.service.configure.Configure;
import com.sghd.fight.module.game.service.match.Matcher;
import com.sghd.fight.module.game.service.match.PveMatcher;
import com.sghd.fight.module.game.service.match.PvpMatcher;
import com.sghd.fight.module.game.service.room.IRoom;
import com.sghd.fight.module.game.service.room.Player;
import com.sghd.fight.module.game.service.room.PvpRoom;
import com.sghd.fight.module.game.service.skill.ConfigService;
import com.sghd.fight.module.game.service.skill.effect.Effect;
import com.sghd.fight.module.game.service.work.RoomRunner;
import com.sghd.fight.module.game.service.work.RoomTimer;
import com.sghd.fight.module.game.service.work.RoomWork;
import com.sghd.fight.net.SocketEnum;
import com.sghd.fight.utils.Callback;
import com.sghd.fight.utils.Identity;
import com.sghd.pojo.candy.fight.dto.SettleTo;
import com.sghd.pojo.candy.player.vo.ReqPlayerVo;
import com.sghd.pojo.fight.game.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;

/**
 * @author frank
 * @date 2018-10-22 16:17
 * @desc 游戏服务
 **/
@Component
@ConsoleBean
public class GameService implements RoomTimer, ApplicationContextAware {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private ApplicationContext applicationContext;
    /** 房间列表*/
    private ConcurrentMap<Long, IRoom> rooms = new ConcurrentHashMap<>();
    private ConcurrentMap<Long, IRoom> player2Rooms = new ConcurrentHashMap<>();
    @Autowired
    private ThreadService threadService;
    @Autowired
    private ConfigService configService;
    @Autowired
    private EventBus eventBus;
    /** 不同类型的战斗配置器 */
    private ConcurrentHashMap<RoomType, Configure> configs = new ConcurrentHashMap<>(RoomType.values().length);
    /**房间匹配器*/
    private Map<RoomType, Matcher> matchers = new HashMap<>();

    @PostConstruct
    void init() {
        for (Configure configure : applicationContext.getBeansOfType(Configure.class).values()) {
            if (configs.containsKey(configure.getType())) {
                logger.error("配置[{}]重复!", configure.getType());
            }
            configs.put(configure.getType(), configure);
        }
        matchers.put(RoomType.PVP, new PvpMatcher(this, configs.get(RoomType.PVP), threadService));
        matchers.put(RoomType.PVE, new PveMatcher(this, configs.get(RoomType.PVE)));
    }

    public IRoom getRoom(long roomId) {
        return rooms.get(roomId);
    }

    public static void main(String[] args) {
        System.out.println(JsonUtils.object2String(new ElementAttr()));
    }

    public void addTimer(IRoom room, RoomRunner runner, Date time) {
        threadService.add(new RoomWork(room, runner, time));
    }

    public void addTimer(RoomWork work) {
        threadService.add(work);
    }

    @Override
    public void submit(IRoom room, RoomRunner runner) {
        threadService.submit(room, runner);
    }

    /**
     * @param work
     */
    @Override
    public void removeWork(RoomWork work) {
        threadService.remove(work);
    }

    @ConsoleCommand(name = "test")
    public void test() {
        try {
            IRoom pvpRoom = new PvpRoom(0, RoomType.PVP, this, configService);
            rooms.put(0l, pvpRoom);
            Player owner = new Player(1, "test1", 0, pvpRoom, new HashMap<>(), new HashMap<>(), 0L);
            Player target = new Player(2, "test2", 1, pvpRoom, new HashMap<>(), new HashMap<>(), 0L);
            pvpRoom.join(owner);
            pvpRoom.join(target);
            int skillId = 1001;
            SkillConfig config = configService.getSkillConfig(skillId);
            System.out.println(target.getName() + "初始HP:" + target.getValue(UnitValue.HP));
            //执行技能 owner技能施放都
            Map<String, Object> ctx = new HashMap<>();
            ctx.put(Effect.LIGHTING, 3);
            owner.execute(pvpRoom, config, ctx);
//            skillId = 1001;
//            config = skills.get(skillId);
//            //执行技能 owner技能施放都
//            owner.execute(pvpRoom, config, new HashMap<>());

            //target 当前 Hp
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @param playerId 玩家ID
     * @param first   选择移动的单元下标
     * @param second  目的地单元下标
     */
    public void move(long playerId, int first, int second, ResultCallback<Result<String>> callback) {
        IRoom room = player2Rooms.get(playerId);
        if (room == null) {
            logger.error("并无该玩家!");
            throw new ManagedException(-1, "并无该玩家");
        }
        threadService.submit(room, (now) -> {
            try {
                if (room.getCurrent().hasState(UnitState.TRUSTEE)) {
                    throw new ManagedException(-1, "托管中不允许移动！");
                }
                room.action(playerId, first, second);
                callback.call(Result.valueOf("true"));
            } catch (ManagedException e) {
                callback.call(Result.valueOf(e.getCode(), e.getMessage()));
            } catch (Exception e) {
                logger.error("玩家交换错误", playerId, e);
                callback.call(Result.valueOf(-1, e.getMessage()));
            }
        });
    }

    //玩家准备
    public void ready(long playerId, ResultCallback<Result<String>> callback) {
        IRoom room = player2Rooms.get(playerId);
        if (room == null) {
            logger.debug("房间不存在！");
            return;
        }
        threadService.submit(room, (now) -> {
            if (room.ready(playerId)) {
                try {
                    callback.call(Result.valueOf("true"));
                } catch (ManagedException e) {
                    //TODO 弹窗
                } catch (Exception e) {
                    logger.error("玩家[{}]准备发生未知错误!", playerId, e);
                }
            }
        });
    }

    /**参加匹配，进入等待队列*/
    @ConsoleCommand(name = "match")
    public void match(long playerId, int bigLevel, int level, RoomType type) {
        //防止重复点击
        synchronized (Identity.valueOf(playerId)) {
            //等待房间结束
            if (player2Rooms.containsKey(playerId)) {
                throw new ManagedException(-1, "存在未结束的战斗！");
            }
            if (playerId < 0) { // 压力测试
                testJoinMatch(playerId, bigLevel, level, type);
            } else {
                ReqPlayerVo reqPlayerVo;
                //获取玩家信息
                if (type == RoomType.PVE) {
                    reqPlayerVo = ReqPlayerVo.valueOf(playerId, new LevelVo(bigLevel, level));
                } else {
                    reqPlayerVo = ReqPlayerVo.valueOf(playerId);
                }

                post(GameServerEvent.NAME, GameServerEvent.valueOf(reqPlayerVo, SocketEnum.REQUEST_PLAYER_INFO, new Callback<PlayerVo>() {
                    @Override
                    public void call(PlayerVo playerInfo) {
                        joinMatch(playerInfo, type, bigLevel, level);
                    }

                    @Override
                    public void onException(Exception ex, PlayerVo body) {
                        logger.error("获取玩家[{}]基本信息失败[{}]", new Object[]{playerId, ex});
                        PlayerVo playerInfo = PlayerVo.valueOf(playerId, "TEST:" + playerId, 0);
                        joinMatch(playerInfo, type, bigLevel, level);
                    }
                }));
            }
        }
    }

    /**
     * 测试匹配
     * @param playerId
     * @param bigLevel
     * @param level
     * @param type
     */

    public void testJoinMatch(long playerId, int bigLevel, int level, RoomType type) {
        PlayerVo playerInfo = PlayerVo.valueOf(playerId, "ROBOT:" + playerId, 0);
        playerInfo.setLevelVo(new LevelVo(bigLevel, level));
        //匹配线程调用
        threadService.submitMatch((now) -> {
            for (Matcher matcher : matchers.values()) {
                if (matcher.containsKey(playerInfo.getPlayerId())) {
                    logger.error("玩家[{}]重复进入房间！");
                    return;
                }
            }
            Matcher matcher = matchers.get(type);
            matcher.add(playerInfo);
            if (logger.isDebugEnabled()) {
                logger.debug("玩家[{}]进入[{}]匹配队列！", playerInfo.getPlayerId(), type);
            }
        });

    }

    /**
     * 进入匹配队列
     * @param playerInfo
     * @param type
     * @param bigLevel
     * @param level
     */
    public void joinMatch(PlayerVo playerInfo, RoomType type, int bigLevel, int level) {
        if (type == RoomType.PVE) {
            if (playerInfo.getLevelVo().getBigLevelId() == -1 && playerInfo.getLevelVo().getLevelId() == -1) {
                playerInfo.setLevelVo(new LevelVo(bigLevel, level));
            } else {
                LevelVo current = new LevelVo(bigLevel, level);
                if (!playerInfo.canMatch(current)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("玩家[{}]越级挑战关卡[{}]！", playerInfo.getPlayerId(), current.getLevelStr());
                    }
                    return;
                } else {
                    playerInfo.setLevelVo(new LevelVo(bigLevel, level));
                }
            }
        }
        //匹配线程调用
        threadService.submitMatch((now) -> {
            for (Matcher matcher : matchers.values()) {
                if (matcher.containsKey(playerInfo.getPlayerId())) {
                    logger.error("玩家[{}]重复进入房间！");
                    return;
                }
            }
            Matcher matcher = matchers.get(type);
            if (logger.isDebugEnabled()) {
                logger.debug("玩家[{}]进入[{}]匹配队列！", playerInfo.getPlayerId(), type);
            }
            matcher.add(playerInfo);
        });
    }

    public void matchReday(long playerId) {
        synchronized (Identity.valueOf(playerId)) {
            threadService.submitMatch((now) -> {
                PvpMatcher matcher = (PvpMatcher) matchers.get(RoomType.PVP);
                matcher.ready(playerId);//玩家确认可以进入
            });
        }
    }

    /**
     * 取消匹配，方法同步返回
     * @param playerId
     */
    public void cancelMatch(long playerId) throws InterruptedException {
        synchronized (Identity.valueOf(playerId)) {
            //匹配线程调用
            CountDownLatch downLatch = new CountDownLatch(1);
            threadService.submitMatch((now) -> {
                try {
                    for (Matcher matcher : matchers.values()) {
                        if (!matcher.containsKey(playerId)) {
                            continue;
                        }
                        matcher.quit(playerId);
                    }
                } finally {
                    downLatch.countDown();
                }
            });
            downLatch.await();
        }
    }

    /**
     * 托管
     * @param playerId
     */
    public void trustee(long playerId) {

    }

    /**
     * 推送事件
     * */
    public void post(String name, Object body) {
        eventBus.post(Event.valueOf(name, body));
    }

    public void syncPost(String name, Object body) {
        eventBus.syncPost(Event.valueOf(name, body));
    }

    /**
     * 匹配成功的房间
     * */
    public void matched(List<IRoom> rooms) {
        rooms.stream().forEach(room -> {
            matched(room);
        });
    }

    public void matched(IRoom room) {
        this.rooms.put(room.getId(), room);
        for (Unit unit : room.getUnits().values()) {
            if (unit.getType() == UnitEnum.PLAYER) {
                player2Rooms.put(unit.getId(), room);
            }
        }
        PushEvent event = PushEvent.valueOf(room.toVo(), room.getUnits().keySet(), SocketEnum._ROOM_INFO);
        post(PushEvent.NAME, event);
    }


    public void execute(long playerId, int skillId, ResultCallback<Result<Object>> callback) {
        IRoom room = player2Rooms.get(playerId);
        if (room == null) {
            throw new ManagedException(-1, "房间未找到！");
        }
        logger.debug("收到执行技能的时间：{}", System.currentTimeMillis());
        threadService.submit(room, (now) -> {
            try {
                room.execute(playerId, skillId);
                callback.call(Result.valueOf(0));
            } catch (ManagedException e) {
                logger.debug("玩家[{}]执行技能[{}]发生错误!", playerId, e.getMessage());
                Map<String, Object> body = new HashMap<String, Object>() {
                    {
                        put("skillId", skillId);
                        put("message", e.getMessage());
                    }
                };
                callback.call(Result.valueOf(e.getCode(), body));
            } catch (Exception e) {
                logger.error("玩家[{}]执行技能[{}]发生未知错误!", playerId, skillId, e);
                callback.call(Result.valueOf(-1, "执行技能发生未知错误!"));
            }
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void test1(long playerId, ResultCallback<Object> callback) {
        IRoom room = player2Rooms.get(playerId);
        if (room == null) {
            return;
        }
        threadService.submit(room, (now) -> {
            try {
                List<Grid> grids = room.test();
                Map<String, Object> body = new HashMap<>();
                body.put("grids", grids);
                callback.call(body);
            } catch (ManagedException e) {
                //TODO 弹窗
            } catch (Exception e) {
                logger.error("玩家[{}]测试托管[{}]发生未知错误!", playerId, e);
            }
        });

    }

    public void endTrustee(long playerId, ResultCallback<Result<String>> callback) {
        IRoom room = player2Rooms.get(playerId);
        if (room == null) {
            return;
        }
        threadService.submit(room, (now) -> {
            try {
                room.endTrustee(playerId);
                callback.call(Result.valueOf("true"));
            } catch (ManagedException e) {
                //TODO 弹窗
                callback.call(Result.valueOf(e.getCode(), e.getMessage()));
            } catch (Exception e) {
                logger.error("玩家[{}]退出托管[{}]发生未知错误!", playerId, e);
            }
        });
    }

    public void exit(Long playerId) {
        IRoom room = player2Rooms.remove(playerId);
        logger.debug("玩家[{}]退出！", playerId);
        if (room != null) {
            room = rooms.remove(room.getId());
            if (room != null) {
                logger.debug("移除房间[{}]", room.getId());
                room.destory();
            }
        }
        room = null;
    }

    public ConcurrentMap<Long, IRoom> getRooms() {
        return rooms;
    }

    /**
     * 离线
     * @param playerId
     */
    public void offline(long playerId) {
        IRoom room = player2Rooms.get(playerId);
        if (room == null) {
            return;
        }
        threadService.submit(room, (now) -> {
            try {
                if (room.isOver()) {
                    return;
                }
                if (room.getType() == RoomType.PVE) {//直接结算
                    room.gameOver(ExitReason.OFFLINE);
                    return;
                }
                Unit player = room.getUnits().get(playerId);
                if (null != player) {
                    logger.debug("玩家[{}]进入离线状态！", playerId);
                    player.addState(UnitState.OFFLINE);//离线状态
                    if (!player.hasState(UnitState.TRUSTEE)) {
                        room.trustee(player);
                    }
                    //如果玩家还没有准备
                    if (!player.getReady()) {
                        room.ready(playerId);
                    }
                }
                boolean offile = true;
                for (Unit unit : room.getUnits().values()) {
                    if (unit.getType() == UnitEnum.ROBOT) {
                        continue;
                    }
                    if (!unit.hasState(UnitState.OFFLINE)) {
                        offile = false;
                        break;
                    }
                }
                if (offile) {
                    room.gameOver(ExitReason.OFFLINE);
                }
            } catch (Exception e) {
                logger.error("玩家[{}]退出托管[{}]发生未知错误!", playerId, e);
            }
        });
    }

    public void reconn(long playerId, ResultCallback<Result<Object>> callback) {
        IRoom room = player2Rooms.get(playerId);
        if (room == null) {
            logger.debug("玩家[{}]重连房间已经结束！", playerId);
            syncPost(GameServerEvent.NAME, GameServerEvent.valueOf("{\"playerId\":\"" + playerId + "\"}", SocketEnum.CLEAN_RECONN, null));
            callback.call(Result.valueOf(-1, "重连房间已经结束"));
            return;
        }
        threadService.submit(room, (now) -> {
            try {
                if (room instanceof PvpRoom) {
                    RoomVo roomVo = ((PvpRoom) room).reconn(playerId);
                    callback.call(Result.valueOf(roomVo));
                }
            } catch (ManagedException e) {
                logger.error("玩家[{}]重连发生未知错误!", playerId, e);
                syncPost(GameServerEvent.NAME, GameServerEvent.valueOf("{\"playerId\":\"" + playerId + "\"}", SocketEnum.CLEAN_RECONN, null));
                callback.call(Result.valueOf(-1, "重连房间已经结束"));
            } catch (Exception e) {
                logger.error("玩家[{}]重连发生未知错误!", playerId, e);
                syncPost(GameServerEvent.NAME, GameServerEvent.valueOf("{\"playerId\":\"" + playerId + "\"}", SocketEnum.CLEAN_RECONN, null));
                callback.call(Result.valueOf(-1, "重连发生未知错误"));
            }
        });
    }

    public void resetSkill(Long playerId, Set<Integer> kepps) {
        IRoom room = player2Rooms.get(playerId);
        if (room == null) {
            throw new ManagedException(-1, "房间已经结束！");
        }
        threadService.submit(room, (now) -> {
            try {
                if (room instanceof PvpRoom) {
                    ((PvpRoom) room).resetSkill(playerId, kepps);
                }
            } catch (ManagedException e) {
                logger.error("玩家[{}]重置技能发生未知错误!", playerId);
                //callback.call(Result.valueOf(e.getCode(), e.getMessage()));
            } catch (Exception e) {
                logger.error("玩家[{}]重置技能发生未知错误!", playerId, e);
                //callback.call(Result.valueOf(-1, e.getMessage()));
            }
        });

    }

    @ConsoleCommand(name = "send")
    public void send() {
        IRoom pvpRoom = new PvpRoom(1, RoomType.PVP, this, configService);
        Callback<SettleVo> callback = new Callback<SettleVo>() {
            @Override
            public void call(SettleVo body) {
                System.out.println(JsonUtils.object2String(body));
            }

            @Override
            public void onException(Exception ex, SettleVo body) {

            }
        };
        SettleTo settleTo = SettleTo.valueOf(1L, 100, 20, 0, 2L, 0, ExitReason.DEAD);
        eventBus.syncPost(Event.valueOf(GameServerEvent.NAME, GameServerEvent.valueOf(settleTo, 12, callback)));
    }

    /**
     * 停服
     */
    public void stop() {
        for (IRoom room : rooms.values()) {
            room.submit(now -> {
                room.gameOver(ExitReason.FORCE);
            });
        }
    }
}
